#include "interpreter.h"
#include <string.h>
#ifdef DEBUGGER
static int gEnableDebugger = true;
#else
static int gEnableDebugger = false;
#endif
#include "table.h"
#include "variable.h"
#include "lex.h"
#include "parse.h"
#include "platform.h"
#include "type.h"
#include "platform.h"
#include <cstdlib/cstdlib.h>
void Interpreter::Initialize(int stackSize) {
    memset(this, '\0', sizeof(Interpreter));
    Platform::PlatformInit(this);
    Cstdlib::BasicIOInit(this);
    HeapInit(stackSize);
    MyTable::TableInit(this);
    Variable::VariableInit(this);
    Lex::LexInit(this);
    Type::TypeInit(this);
    IncludeInit();
    LibraryInit();
    Platform::PlatformLibraryInit(this);
#ifdef DEBUGGER
    DebugInit(pc);
#endif
}

void Interpreter::PicocCleanup() {
#ifdef DEBUGGER
    DebugCleanup(this);
#endif
    IncludeCleanup();
    Parse::ParseCleanup(this);
    Lex::LexCleanup(this);
    Variable::VariableCleanup(this);
    Type::TypeCleanup(this);
    MyTable::TableStrFree(this);
    HeapCleanup();
    Platform::PlatformCleanup(this);
}
#define CALL_MAIN_NO_ARGS_RETURN_VOID "main();"
#define CALL_MAIN_WITH_ARGS_RETURN_VOID "main(__argc,__argv);"
#define CALL_MAIN_NO_ARGS_RETURN_INT "__exit_value = main();"
#define CALL_MAIN_WITH_ARGS_RETURN_INT "__exit_value = main(__argc,__argv);"

void Interpreter::PicocCallMain(int argc, char **argv) {
    /* check if the program wants arguments */
    Value *funcValue = NULL;

    if (!(this, MyTable::TableStrRegister(this, "main")))
        Platform::ProgramFailNoParser(this, "main() is not defined");

    (this, NULL, MyTable::TableStrRegister(this, "main"), &funcValue);
    if (funcValue->typ->base != kTypeFunction)
        Platform::ProgramFailNoParser(this, "main is not a function - can't call it");

    if (funcValue->val->FuncDef.numParams != 0) {
        /* define the arguments */
        Variable::VariableDefinePlatformVar(this, NULL, "__argc", &this->intType,
                                  (AnyValue *)&argc, false);
        Variable::VariableDefinePlatformVar(this, NULL, "__argv", this->charPtrPtrType,
                                  (AnyValue *)&argv, false);
    }

    if (funcValue->val->FuncDef.returnType == &this->voidType) {
        if (funcValue->val->FuncDef.numParams == 0)
            Parse::PicocParse(this, "startup", CALL_MAIN_NO_ARGS_RETURN_VOID,
                       strlen(CALL_MAIN_NO_ARGS_RETURN_VOID), true, true, false,
                       gEnableDebugger);
        else
            Parse::PicocParse(this, "startup", CALL_MAIN_WITH_ARGS_RETURN_VOID,
                       strlen(CALL_MAIN_WITH_ARGS_RETURN_VOID), true, true, false,
                       gEnableDebugger);
    } else {
        Variable::VariableDefinePlatformVar(this, NULL, "__exit_value", &this->intType,
                                  (AnyValue *)&this->picocExitValue, true);

        if (funcValue->val->FuncDef.numParams == 0)
            Parse::PicocParse(this, "startup", CALL_MAIN_NO_ARGS_RETURN_INT,
                       strlen(CALL_MAIN_NO_ARGS_RETURN_INT), true, true, false,
                       gEnableDebugger);
        else
            Parse::PicocParse(this, "startup", CALL_MAIN_WITH_ARGS_RETURN_INT,
                       strlen(CALL_MAIN_WITH_ARGS_RETURN_INT), true, true, false,
                       gEnableDebugger);
    }
}
void Interpreter::PicocPlatformScanFile(const char *fileName) {
    char *SourceStr = PlatformReadFile(fileName);
    Parse::PicocParse(this, fileName, SourceStr, strlen(SourceStr), true, false, true,
               gEnableDebugger);

}

/* read a file into memory */
char *Interpreter::PlatformReadFile(const char *fileName) {
    struct stat fileInfo;
    char *readText;
    FILE *inFile;
    int bytesRead;
    char *p;

    if (stat(fileName, &fileInfo))
        Platform::ProgramFailNoParser(this, "can't read file %s\n", fileName);

    readText = (char *)malloc(fileInfo.st_size + 1);
    if (readText == NULL)
        Platform::ProgramFailNoParser(this, "out of memory\n");

    inFile = fopen(fileName, "r");
    if (inFile == NULL)
        Platform::ProgramFailNoParser(this, "can't read file %s\n", fileName);

    bytesRead = fread(readText, 1, fileInfo.st_size, inFile);
    if (bytesRead == 0)
        Platform::ProgramFailNoParser(this, "can't read file %s\n", fileName);

    readText[bytesRead] = '\0';
    fclose(inFile);

    if ((readText[0] == '#') && (readText[1] == '!')) {
        for (p = readText; (*p != '\r') && (*p != '\n'); ++p) {
            *p = ' ';
        }
    }

    return readText;
}
/* initialize the built-in include libraries */
void Interpreter::IncludeInit() {
    IncludeRegister("ctype.h", NULL, &Cstdlib::StdCtypeFunctions[0], NULL);
    IncludeRegister("errno.h", &Cstdlib::StdErrnoSetupFunc, NULL, NULL);
# ifndef NO_FP
    IncludeRegister("math.h", &Cstdlib::MathSetupFunc, &Cstdlib::MathFunctions[0], NULL);
# endif
    IncludeRegister("stdbool.h", &Cstdlib::StdboolSetupFunc, NULL, Cstdlib::kStdboolDefs);
    IncludeRegister("stdio.h", &Cstdlib::StdioSetupFunc, &Cstdlib::StdioFunctions[0], Cstdlib::kStdioDefs);
    IncludeRegister("stdlib.h", &Cstdlib::StdlibSetupFunc, &Cstdlib::StdlibFunctions[0], NULL);
    IncludeRegister("string.h", &Cstdlib::StringSetupFunc, &Cstdlib::StringFunctions[0], NULL);
    IncludeRegister("time.h", &Cstdlib::StdTimeSetupFunc, &Cstdlib::StdTimeFunctions[0], Cstdlib::kStdTimeDefs);
# ifndef WIN32
    IncludeRegister(pc, "unistd.h", &UnistdSetupFunc, &UnistdFunctions[0], UnistdDefs);
# endif
}

/* clean up space used by the include system */
void Interpreter::IncludeCleanup() {
    IncludeLibrary *thisInclude = includeLibList;
    IncludeLibrary *nextInclude;

    while (thisInclude != NULL) {
        nextInclude = thisInclude->nextLib;
        HeapFreeMem(thisInclude);
        thisInclude = nextInclude;
    }

    includeLibList = NULL;
}

/* register a new build-in include file */
void Interpreter::IncludeRegister(const char *includeName,
                     void (*setupFunction)(Interpreter *pc), const LibraryFunction *funcList,
                     const char *setupCSource) {
    IncludeLibrary *newLib = (IncludeLibrary *)HeapAllocMem(sizeof(IncludeLibrary));
    newLib->includeName = MyTable::TableStrRegister(this, includeName);
    newLib->setupFunction = setupFunction;
    newLib->funcList = funcList;
    newLib->setupCSource = setupCSource;
    newLib->nextLib = includeLibList;
    includeLibList = newLib;
}

/* include all of the system headers */
void Interpreter::PicocIncludeAllSystemHeaders() {
    IncludeLibrary *thisInclude = includeLibList;

    for (; thisInclude != NULL; thisInclude = thisInclude->nextLib)
        IncludeFile(thisInclude->includeName);
}

/* include one of a number of predefined libraries, or perhaps an actual file */
void Interpreter::IncludeFile(char *fileName) {
    IncludeLibrary *lInclude;

    /* scan for the include file name to see if it's in our list
        of predefined includes */
    for (lInclude = includeLibList; lInclude != NULL;
         lInclude = lInclude->nextLib) {
        if (strcmp(lInclude->includeName, fileName) == 0) {
            /* found it - protect against multiple inclusion */
            if (!(this, fileName)) {
                (this, NULL, fileName, NULL, &voidType, false);

                /* run an extra startup function if there is one */
                if (lInclude->setupFunction != NULL)
                    (*lInclude->setupFunction)(this);

                /* parse the setup C source code - may define types etc. */
                if (lInclude->setupCSource != NULL)
                    Parse::PicocParse(this, fileName, lInclude->setupCSource,
                               strlen(lInclude->setupCSource), true, true, false, false);

                /* set up the library functions */
                if (lInclude->funcList != NULL)
                    LibraryAdd(lInclude->funcList);
            }

            return;
        }
    }

    /* not a predefined file, read a real file */
    PicocPlatformScanFile(fileName);
}
#ifdef DEBUG_HEAP
void Interpreter::ShowBigList() {
    struct AllocNode *LPos;

    printf("Heap: bottom=0x%lx 0x%lx-0x%lx, big freelist=", (long)pc->heapBottom,
           (long)&(pc->heapMemory)[0], (long)&(pc->heapMemory)[HEAP_SIZE]);
    for (LPos = pc->freeListBig; LPos != NULL; LPos = LPos->nextFree)
        printf("0x%lx:%d ", (long)LPos, LPos->size);

    printf("\n");
}
#endif

/* initialize the stack and heap storage */
void Interpreter::HeapInit(int stackOrHeapSize) {
    int alignOffset = 0;

    heapMemory = (unsigned char *)malloc(stackOrHeapSize);
    heapBottom = NULL;  /* the bottom of the (downward-growing) heap */
    stackFrame = NULL;  /* the current stack frame */
    heapStackTop = NULL;  /* the top of the stack */

    while (((unsigned long)&heapMemory[alignOffset] & (sizeof(ALIGN_TYPE) - 1)) != 0)
        alignOffset++;

    stackFrame = &(heapMemory)[alignOffset];
    heapStackTop = &(heapMemory)[alignOffset];
    *(void **)(stackFrame) = NULL;
    heapBottom =
        &(heapMemory)[stackOrHeapSize - sizeof(ALIGN_TYPE) + alignOffset];
    freeListBig = NULL;
    for (int i = 0; i < FREELIST_BUCKETS; i++)
        freeListBucket[i] = NULL;
}

void Interpreter::HeapCleanup() {
    free(heapMemory);
}

/* allocate some space on the stack, in the current stack frame
 * clears memory. can return NULL if out of stack space */
void *Interpreter::HeapAllocStack(int size) {
    Interpreter pc = Interpreter::GetInstance();
    char *newMem = (char *)pc.heapStackTop;
    char *newTop = (char *)pc.heapStackTop + MEM_ALIGN(size);
#ifdef DEBUG_HEAP
    printf("HeapAllocStack(%ld) at 0x%lx\n", (unsigned long)MEM_ALIGN(size),
           (unsigned long)heapStackTop);
#endif
    if (newTop > (char *)pc.heapBottom)
        return NULL;

    pc.heapStackTop = (void *)newTop;
    memset((void *)newMem, '\0', size);
    return newMem;
}

/* allocate some space on the stack, in the current stack frame */
void Interpreter::HeapUnpopStack(int size) {
    Interpreter pc = Interpreter::GetInstance();
#ifdef DEBUG_HEAP
    printf("HeapUnpopStack(%ld) at 0x%lx\n", (unsigned long)MEM_ALIGN(size),
           (unsigned long)heapStackTop);
#endif
    pc.heapStackTop = (void *)((char *)pc.heapStackTop + MEM_ALIGN(size));
}

/* free some space at the top of the stack */
int Interpreter::HeapPopStack(void *Addr, int size) {
    Interpreter pc = Interpreter::GetInstance();
    int toLose = MEM_ALIGN(size);
    if (toLose > ((char *)pc.heapStackTop - (char *)&(pc.heapMemory)[0]))
        return false;

#ifdef DEBUG_HEAP
    printf("HeapPopStack(0x%lx, %ld) back to 0x%lx\n", (unsigned long)Addr,
           (unsigned long)MEM_ALIGN(size), (unsigned long)heapStackTop - toLose);
#endif
    pc.heapStackTop = (void *)((char *)pc.heapStackTop - toLose);
    assert(Addr == NULL || pc.heapStackTop == Addr);

    return true;
}

/* push a new stack frame on to the stack */
void Interpreter::HeapPushStackFrame() {
    Interpreter pc = Interpreter::GetInstance();
#ifdef DEBUG_HEAP
    printf("Adding stack frame at 0x%lx\n", (unsigned long)heapStackTop);
#endif
    *(void **)pc.heapStackTop = pc.stackFrame;
    pc.stackFrame = pc.heapStackTop;
    pc.heapStackTop = (void *)((char *)pc.heapStackTop +
                                MEM_ALIGN(sizeof(ALIGN_TYPE)));
}

/* pop the current stack frame, freeing all memory in the
    frame. can return NULL */
int Interpreter::HeapPopStackFrame() {
    Interpreter pc = Interpreter::GetInstance();
    if (*(void **)pc.stackFrame != NULL) {
        pc.heapStackTop = pc.stackFrame;
        pc.stackFrame = *(void **)pc.stackFrame;
#ifdef DEBUG_HEAP
        printf("Popping stack frame back to 0x%lx\n",
               (unsigned long)heapStackTop);
#endif
        return true;
    } else
        return false;
}

/* allocate some dynamically allocated memory. memory is cleared.
    can return NULL if out of memory */
void *Interpreter::HeapAllocMem(int size) {
    return calloc(size, 1);
}

/* free some dynamically allocated memory */
void Interpreter::HeapFreeMem(void *mem) {
    free(mem);
}



/* global initialisation for libraries */
void Interpreter::LibraryInit() {

    /* define the version number macro */
    versionString = MyTable::TableStrRegister(this, PICOC_VERSION);
    Variable::VariableDefinePlatformVar(this, NULL, "PICOC_VERSION", charPtrType,
     (AnyValue *)&versionString, false);

    /* define endian-ness macros */
    bigEndian = ((*(char *)&__ENDIAN_CHECK__) == 0);
    littleEndian = ((*(char *)&__ENDIAN_CHECK__) == 1);

    Variable::VariableDefinePlatformVar(this, NULL, "BIG_ENDIAN", &intType,
     (AnyValue *)&bigEndian, false);
    Variable::VariableDefinePlatformVar(this, NULL, "LITTLE_ENDIAN", &intType,
     (AnyValue *)&littleEndian, false);
}

/* add a library */
void Interpreter::LibraryAdd(const LibraryFunction *funcList) {
    Interpreter pc = Interpreter::GetInstance();
    ParseState parser;
    char *identifier;
    ValueType *returnType;
    Value *newValue;
    void *tokens;
    char *intrinsicName = MyTable::TableStrRegister(this, "c library");

    /* read all the library definitions */
    for (int i = 0; funcList[i].prototype != NULL; i++) {
        tokens = Lex::LexAnalyse(this, (const char *)intrinsicName, funcList[i].prototype,
                            strlen((char *)funcList[i].prototype), NULL);
        Lex::LexInitParser(this, &parser, funcList[i].prototype, tokens,
                      intrinsicName, true, false);
        Type::TypeParse(&pc, &parser, &returnType, &identifier, NULL);
        newValue = Parse::ParseFunctionDefinition(&pc, &parser, returnType, identifier);
        newValue->val->FuncDef.intrinsic = (void (*)(ParseState *, Value *, Value **, int))(funcList[i].func);
        HeapFreeMem(tokens);
    }
}

/* print a type to a stream without using printf/sprintf */
void Interpreter::PrintType(ValueType *typ, IOFILE *stream) {
    switch (typ->base) {
    case kTypeVoid:
        Cstdlib::PrintStr("void", stream);
        break;
    case kTypeInt:
        Cstdlib::PrintStr("int", stream);
        break;
    case kTypeShort:
        Cstdlib::PrintStr("short", stream);
        break;
    case kTypeChar:
        Cstdlib::PrintStr("char", stream);
        break;
    case kTypeLong:
        Cstdlib::PrintStr("long", stream);
        break;
    case kTypeUnsignedInt:
        Cstdlib::PrintStr("unsigned int", stream);
        break;
    case kTypeUnsignedShort:
        Cstdlib::PrintStr("unsigned short", stream);
        break;
    case kTypeUnsignedLong:
        Cstdlib::PrintStr("unsigned long", stream);
        break;
    case kTypeUnsignedChar:
        Cstdlib::PrintStr("unsigned char", stream);
        break;
    case kTypeFP:
        Cstdlib::PrintStr("double", stream);
        break;
    case kTypeFunction:
        Cstdlib::PrintStr("function", stream);
        break;
    case kTypeMacro:
        Cstdlib::PrintStr("macro", stream);
        break;
    case kTypePointer:
        if (typ->fromType)
            PrintType(typ->fromType, stream);
        Cstdlib::PrintCh('*', stream);
        break;
    case kTypeArray:
        PrintType(typ->fromType, stream);
        Cstdlib::PrintCh('[', stream);
        if (typ->arraySize != 0)
            Cstdlib::PrintSimpleInt(typ->arraySize, stream);
        Cstdlib::PrintCh(']', stream);
        break;
    case kTypeStruct:
        Cstdlib::PrintStr("struct ", stream);
        Cstdlib::PrintStr(typ->identifier, stream);
        break;
    case kTypeUnion:
        Cstdlib::PrintStr("union ", stream);
        Cstdlib::PrintStr(typ->identifier, stream);
        break;
    case kTypeEnum:
        Cstdlib::PrintStr("enum ", stream);
        Cstdlib::PrintStr(typ->identifier, stream);
        break;
    case kTypeGotoLabel:
        Cstdlib::PrintStr("goto label ", stream);
        break;
    case kTypeType:
        Cstdlib::PrintStr("type ", stream);
        break;
    }
}

Interpreter::Interpreter() {}
